/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			OpenGLBind.d
	Description:	resource bind stuff
	Date:			25.11.2007 by Digited
*/
module moonglide.renderer.opengl.OpenGLBind;

import
	moonglide.util.MoonUtil,

	moonglide.renderer.opengl.GL,
	moonglide.renderer.opengl.OpenGLRenderer,
	moonglide.renderer.opengl.OpenGLShaderWork,

	moonglide.renderer.IRendererBind,
	moonglide.renderer.IRendererRes;

//=============================================================
/// COpenGLRendererBind - opengl resource bind stuff
//=============================================================
class COpenGLRendererBind : IRendererBind
{
	protected:
		CResTexture	_curTexID;
		CResMesh	_curMeshID;

		bool		_blend,
					_cull,
					_alpha,
					_light,
					_depthT,
					_simpleMeshBind = false;

        ubyte		_depthMask;

		CEngLight[] _curLights;


		void setBlend( bool SetB )
		{
			if( SetB!= _blend )
			{
				_blend = SetB;
				( _blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND) );
			}
		}

		void setCull( bool SetB )
		{
			if( SetB!= _cull )
			{
				_cull = SetB;
				( _cull ? glEnable(GL_CULL_FACE) : glDisable(GL_CULL_FACE) );
			}
		}

		void setDepthTest( bool SetB )
		{
			if( SetB!= _depthT )
			{
				_depthT = SetB;
				( _depthT ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST) );
			}
		}

		void setDepthMask( ubyte SetB )
		{
			if( SetB!= _depthMask )
			{
				_depthMask = SetB;
				glDepthMask(SetB);
			}
		}

		void setLight( bool SetB )
		{
			if( SetB!= _light )
			{
				_light = SetB;
				( _light ? glEnable( GL_LIGHTING ) : glDisable( GL_LIGHTING ));
			}
		}

		void setAlpha( bool SetB )
		{
			if( SetB!= _alpha )
			{
				_alpha = SetB;
				( _alpha ? glEnable(GL_ALPHA_TEST) : glDisable(GL_ALPHA_TEST) );
			}
		}

		COpenGLCaps		_caps;
		COpenGLShader	_shaderMan;
		IRendererRes    _resLoader;
	public:

        /**
         *  simple way of binding meshes.
         *  if simpleMeshBind, binding system use only "Index" and "Vertex" fields of Mesh
         *  use for zOnly render pass, or in occlusion queries
         */
        void simpleMeshBind(bool b)
        {
            _simpleMeshBind = b;
        }
        /// ditto
        bool simpleMeshBind()
        {
            return _simpleMeshBind;
        }

	//---texture----------------------
		void bindTexture( CResTexture tex )
		{
GetGLError(__FILE__,__LINE__);
			if ( !tex )
			{
				debug debugLogY("Warning: Try to bind null texture!");
				return;
			}

			if( !tex.loaded )
			{
				debug attentionLogR( "Trying to bind unload texture: " ~ tex.id );
				return;
			}

			//debug writefln( "binded for ", tex.getID(), ": ", tex.TextureID );
//debugLog.trace( Integer.toString(tex.width) );
//debugLog.trace( Integer.toString(tex.height) );
			if ( tex.needToUpdate )
			{
				_curTexID = null;
				_resLoader.upLoadTexture( tex );
			}

            if( tex.data.length > 0 )
            if( tex.data[0].format == IL_MOON_DEPTH24 )
            {
//                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
//                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
            }
GetGLError(__FILE__,__LINE__);
			if( tex.textureType == Tex_Cube )
			{
				glEnable ( GL_TEXTURE_CUBE_MAP_ARB );

				static Matrix44f tmp;
				if( tex.swapCube )
					tmp = Matrix44f(COpenGLRenderer().curCam.worientation * Matrix33.scale(-1,-1,-1));
				else
					tmp = Matrix44f(COpenGLRenderer().curCam.worientation);
			//	tmp.transpose();

				glMatrixMode ( GL_TEXTURE );
					glPushMatrix();
					glLoadMatrixf ( tmp.ptr );
				glMatrixMode ( GL_MODELVIEW );

				glTexParameteri ( GL_TEXTURE_CUBE_MAP_ARB,  GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri ( GL_TEXTURE_CUBE_MAP_ARB,  GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

				glEnable(GL_TEXTURE_GEN_S);
				glEnable(GL_TEXTURE_GEN_T);
				glEnable(GL_TEXTURE_GEN_R);

				glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
				glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
				glTexGeni ( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB );
			}

			//if( _curTexID !is Tex )
			{
				glBindTexture( tex.tType, tex.textureID );
				_curTexID = tex;
			}
		}


		void unBindTexture( CResTexture tex )
		in
		{
			assert( tex);
		}
		body
		{
            if( !tex.loaded )
			{
				debug attentionLogR( "Trying to unbind unload texture: " ~ tex.id );
				return;
			}
GetGLError(__FILE__,__LINE__);
            if( tex.data.length > 0 )
            if( tex.data[0].format == IL_MOON_DEPTH24 )
            {
//                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
            }


			if( tex.textureType == Tex_Cube )
			{
				glMatrixMode ( GL_TEXTURE );
					glPopMatrix();
				glMatrixMode ( GL_MODELVIEW );


				glDisable ( GL_TEXTURE_CUBE_MAP_ARB );
				glDisable(GL_TEXTURE_GEN_S);
				glDisable(GL_TEXTURE_GEN_T);
				glDisable(GL_TEXTURE_GEN_R);
			}

	///		glBindTexture( tex.tType, 0 );
			_curTexID = null;
		}

	//---mesh-------------------------
		void bindMesh( CResMesh mesh )
		{
			if ( !mesh )
			{
				debug debugLog.trace("Warning: Try to bind null mesh!");
				return;
			}

			if ( mesh.needToUpdate )
			{
				_curMeshID = null;
				_resLoader.upLoadRes( mesh );
			}

			if( _curMeshID !is mesh )
			{
				_curMeshID = mesh;

				bool vbo_loaded = _caps.useVBO;

				if( vbo_loaded )
					if( mesh.needUpload )
						if( !mesh.loaded )
						{
							debug traceLog.trace( mesh.id ~ " is uploading" );
							mesh.loadToDevice();
							vbo_loaded = mesh.loaded;
						}


				bool BindField(CResMeshField Field, out void* Pointer)
				{
					if( Field.GetLength() == 0 )
						return false;

					if( vbo_loaded )
					{
						static uint para;
						para = Field.countData == 1 ? GL_ELEMENT_ARRAY_BUFFER_ARB : GL_ARRAY_BUFFER_ARB;
						glBindBufferARB( para, Field.vboBuff );
						Pointer = null;
					}
					else
						Pointer = Field.GetData();

					return true;
				}

				static void* xPointer = null;

                if ( !simpleMeshBind )
                {
                    if ( BindField( _curMeshID.fields["Normal"], xPointer ) )
                    {
                        glEnableClientState( GL_NORMAL_ARRAY );
                        glNormalPointer( GL_FLOAT, 0, xPointer );
                    }

                    if ( BindField( _curMeshID.fields["Color"], xPointer ) )
                    {
                        glEnableClientState( GL_COLOR_ARRAY );
                        glColorPointer( _curMeshID.fields["Color"].countData, GL_FLOAT, 0, xPointer );
                    }

                    if( _caps.useMultiTex )
                    {
                        if ( BindField( _curMeshID.fields["Tangent"], xPointer ) )
                        {
                            glClientActiveTextureARB(GL_TEXTURE1_ARB);
                            glEnableClientState( GL_TEXTURE_COORD_ARRAY );

                            glTexCoordPointer( _curMeshID.fields["Tangent"].countData, GL_FLOAT, 0, xPointer );
                        }

                        if ( BindField( _curMeshID.fields["BiNormal"], xPointer ) )
                        {
                            glClientActiveTextureARB(GL_TEXTURE2_ARB);
                            glEnableClientState( GL_TEXTURE_COORD_ARRAY );

                            glTexCoordPointer( _curMeshID.fields["BiNormal"].countData, GL_FLOAT, 0, xPointer );
                        }
                        glClientActiveTextureARB(GL_TEXTURE0_ARB);
                    }

                    if ( BindField( _curMeshID.fields["TexCoord"], xPointer ) )
                    {
                        glEnableClientState( GL_TEXTURE_COORD_ARRAY );
                        glTexCoordPointer( _curMeshID.fields["TexCoord"].countData, GL_FLOAT, 0, xPointer );
                    }
                }   // end of not simpleMeshBin

				BindField( _curMeshID.fields["Index"], xPointer );

				if ( BindField( _curMeshID.fields["Vertex"], xPointer ) )
				{
					glEnableClientState( GL_VERTEX_ARRAY );
					glVertexPointer( _curMeshID.fields["Vertex"].countData, GL_FLOAT, 0, xPointer );
				}
			}
		}


		void unBindMesh( CResMesh mesh )
		{
			//// Unbind
			glDisableClientState( GL_COLOR_ARRAY );
			glDisableClientState( GL_NORMAL_ARRAY );

			if( _caps.useMultiTex )
			{
				glClientActiveTextureARB(GL_TEXTURE2_ARB);
				glDisableClientState( GL_TEXTURE_COORD_ARRAY );
				glClientActiveTextureARB(GL_TEXTURE1_ARB);
				glDisableClientState( GL_TEXTURE_COORD_ARRAY );
				glClientActiveTextureARB(GL_TEXTURE0_ARB);
			}

			glDisableClientState( GL_TEXTURE_COORD_ARRAY );
			glDisableClientState( GL_VERTEX_ARRAY );
			_curMeshID = null;
		}


	//---shader-----------------------
		void bindShader( CResShaderComposite Sha )
		{
			if( Sha.enabled )
				_shaderMan.BindShader(Sha);
		}

		void unBindShader( CResShaderComposite Sha )
		{
			if( Sha.enabled )
				_shaderMan.UnBindShader(Sha);
		}

	//---material---------------------

		uint blendSFactTypeGL( EMatBlendSFactor engType )
		{
			switch(engType)
			{
				case EMatBlendSFactor.one:
					return GL_ONE;
				case EMatBlendSFactor.zero:
					return GL_ZERO;
				case EMatBlendSFactor.dst_color:
					return GL_DST_COLOR;
				case EMatBlendSFactor.one_min_dst_color:
					return GL_ONE_MINUS_DST_COLOR;
				case EMatBlendSFactor.src_alpha:
					return GL_SRC_ALPHA;
				case EMatBlendSFactor.one_min_src_alpha:
					return GL_ONE_MINUS_SRC_ALPHA;
				case EMatBlendSFactor.dst_alpha:
					return GL_DST_ALPHA;
				case EMatBlendSFactor.one_min_dst_alpha:
					return GL_ONE_MINUS_DST_ALPHA;
				case EMatBlendSFactor.src_alpha_sat:
					return GL_SRC_ALPHA_SATURATE;
				default:
					moonAssert( 0, "Error: default switch called in OpenGLBind.BlendSFactTypeGL", __FILE__, __LINE__ );
			}
		}


		uint blendDFactTypeGL( EMatBlendDFactor engType )
		{
			switch(engType)
			{
				case EMatBlendDFactor.zero:
					return GL_ZERO;
				case EMatBlendDFactor.one:
					return GL_ONE;
				case EMatBlendDFactor.src_color:
					return GL_SRC_COLOR;
				case EMatBlendDFactor.one_min_src_color:
					return GL_ONE_MINUS_SRC_COLOR;
				case EMatBlendDFactor.src_alpha:
					return GL_SRC_ALPHA;
				case EMatBlendDFactor.one_min_src_alpha:
					return GL_ONE_MINUS_SRC_ALPHA;
				case EMatBlendDFactor.dst_alpha:
					return GL_DST_ALPHA;
				case EMatBlendDFactor.one_min_dst_alpha:
					return GL_ONE_MINUS_DST_ALPHA;
				default:
					moonAssert( 0, "Error: default switch called in OpenGLBind.BlendDFactTypeGL", __FILE__, __LINE__ );
			}
		}


		void bindMaterial( CResMaterial mat )
		{

			if( !mat )
			{
				unBindMaterial( mat );
				return;
			}


             // NOTE (Jura#1#): If use HDR_hack of FBO - shader must be public
             /// hack :(
            if ( mat.bools["specialShaderUse"] )
            {
           GetGLError(__FILE__,__LINE__);
                if( !mat.loaded )
                    mat.loadToDevice();
            GetGLError(__FILE__,__LINE__);
                if( mat.shader !is null )
                    bindShader( mat.shader );
           GetGLError(__FILE__,__LINE__);




 			if( !mat.loaded )
				mat.loadToDevice();

            if( mat.textures.length > 0 )
            {
                glEnable(GL_TEXTURE_2D);

                if( _caps.useMultiTex )
                {
                    for( int i = mat.textures.length - 1; i >= 0; --i )
                    {
GetGLError(__FILE__,__LINE__);
                        glActiveTextureARB( GL_TEXTURE0_ARB + i );
GetGLError(__FILE__,__LINE__);
                        bindTexture( mat.textures[ i ] );
GetGLError(__FILE__,__LINE__);
                    }
                }   else
                    bindTexture( mat.textures[ 0 ] );

            }
             else
                glDisable(GL_TEXTURE_2D);



                return;
            }

			if( !mat.loaded )
				mat.loadToDevice();

            if( mat.textures.length > 0 )
            {
                glEnable(GL_TEXTURE_2D);

                if( _caps.useMultiTex )
                {
                    for( int i = mat.textures.length - 1; i >= 0; --i )
                    {
                        glActiveTextureARB( GL_TEXTURE0_ARB + i );
                        bindTexture( mat.textures[ i ] );
                    }
                }   else
                    bindTexture( mat.textures[ 0 ] );

            }
             else
                glDisable(GL_TEXTURE_2D);




			glColor4fv( mat.vectors4["color"].ptr );

			setLight( (mat.bools["Lighting"]) );
			setAlpha( (mat.bools["AlphaTest"]) );
			glAlphaFunc( GL_GEQUAL, mat.floats["AlphaPor"] );

			glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT,  mat.vectors4["ambient"].ptr );
			glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE,  mat.vectors4["diffuse"].ptr );
			glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, mat.vectors4["specular"].ptr );
			glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, mat.vectors4["emission"].ptr );
			glMateriali( GL_FRONT_AND_BACK, GL_SHININESS, mat.ints["shininess"] );

			setDepthMask( mat.ints["DepthMask"] );
			setCull( mat.bools["BackCull"] );
			setDepthTest( mat.bools["DepthTest"] );
			setBlend( mat.bools["Blend"] );


			with( mat )
			if ( bools["DepthRanged"] )
			{
				glDepthRange( floats["DepthRangeNear"], floats["DepthRangeFar"] );
			}

			with( mat )
			if( bools["clamp_use"] )
			{
				glMatrixMode( GL_TEXTURE );
				glPushMatrix();

				Matrix44f M = Matrix44f.scale( floats["clampU_max"] - floats["clampU_min"], floats["clampV_max"] - floats["clampV_min"] , 1);
				M.setTranslation( Vector3f( floats["clampU_min"], floats["clampV_min"], 0) );

				glLoadMatrixf(M.ptr);
				glMatrixMode( GL_MODELVIEW );

				//glPopMatrix();
			}

			if( mat.shader !is null )
				bindShader( mat.shader );


			if( mat.bools["Blend"] )
				glBlendFunc( blendSFactTypeGL( mat.sourceBlendFactor ),
							 blendDFactTypeGL( mat.destBlendFactor ));
		}


		void unBindMaterial( CResMaterial mat )
		{
		    glColor4f(1,1,1,1);

			//// Unbind
GetGLError(__FILE__,__LINE__);
             // NOTE (Jura#1#): If use HDR_hack of FBO - shader must be public
            if ( mat.bools["specialShaderUse"] )
            {
                if( mat.shader !is null )
                    unBindShader( mat.shader );

                if( mat.textures.length > 0 )
                {
                    glEnable(GL_TEXTURE_2D);

                    if( _caps.useMultiTex )
                    {
                        for( int i = mat.textures.length - 1; i >= 0; --i )
                        {
                            glActiveTextureARB( GL_TEXTURE0_ARB + i );
                            unBindTexture( mat.textures[ i ] );
                        }
                    }   else
                        unBindTexture( mat.textures[ 0 ] );

                } else
                    glDisable(GL_TEXTURE_2D);
GetGLError(__FILE__,__LINE__);
                return;
            }
GetGLError(__FILE__,__LINE__);

			if( mat.shader !is null )
				unBindShader( mat.shader );



			setAlpha( false );


            if( mat.textures.length > 0 )
            {
                glEnable(GL_TEXTURE_2D);

                if( _caps.useMultiTex )
                {
                    for( int i = mat.textures.length - 1; i >= 0; --i )
                    {
                        glActiveTextureARB( GL_TEXTURE0_ARB + i );
                        unBindTexture( mat.textures[ i ] );
                    }
                }   else
                    unBindTexture( mat.textures[ 0 ] );

            } else
                glDisable(GL_TEXTURE_2D);


			if( mat.bools["clamp_use"] )
			{
				glMatrixMode( GL_TEXTURE );
				glPopMatrix();
				glMatrixMode( GL_MODELVIEW );
			}

			if ( mat.bools["DepthRanged"] )
			{
				glDepthRange( 0.0f, 1.0f );
			}

            setDepthMask( 1 );
			if ( _blend )
			{

				setBlend( false );
				_blend = false;
			}
		}

	//---light------------------------
		void bindLight( CEngLight Lig )
		{
			if ( !Lig )
			{
				debug debugLog.trace("Warning: Try to bind null light!");
				return;
			}

			uint getLightIndx()
			{
				// find free slot for light
				return 0;
			}

			uint indx = getLightIndx();
			assert( indx <= _caps.maxLights );

			uint _indx = GL_LIGHT0 + indx;

			glPushMatrix();

			Matrix44f tmpmat;
			tmpmat.set( COpenGLRenderer().curCam.worientation );


			glLoadMatrixf( tmpmat.ptr );

			Vector3f xpos = Lig.wposition - COpenGLRenderer().curGeom.wposition;

			//xpos = COpenGLRenderer().curGeom.worientation * xpos;


			glLightfv( _indx, GL_POSITION, Vector4f(xpos,0).ptr );
			glLightfv( _indx, GL_AMBIENT, Lig.Ambient.ptr );
			glLightfv( _indx, GL_DIFFUSE, Lig.Diffuse.ptr );
			glLightfv( _indx, GL_SPECULAR, Lig.Specular.ptr );
			glEnable( _indx );

			glPopMatrix();

			curLights[indx] = Lig;
		}


	//---------------------------------
		void unBindLight( CEngLight Lig )
		in
		{
			assert( Lig );
		}
		body
		{
			uint getLightIndx()
			{
				// find slot of this light
				return 0;
			}

			uint indx = getLightIndx;

			assert( curLights[indx] );

			glDisable( GL_LIGHT0 + indx );
			curLights[indx] = null;

		}

		void unBindLights()
		{
		    for ( int i = 0; i<8; ++i )
		    		glDisable( GL_LIGHT0 + i );

			foreach( index, inout li; curLights )
			{
				if( li )
					glDisable( GL_LIGHT0 + index );
			}
		}


		CEngLight[] curLights() { return _curLights; }


		//=============================================================
		/// constructor & destructor stuff
		//=============================================================
		this( COpenGLCaps ca, COpenGLShader sha, IRendererRes res )
		{
			_caps = ca;
			_shaderMan = sha;
			_resLoader = res;
			_curLights.length = _caps.maxLights;

			_blend = false;
                glDisable( GL_BLEND );
			_cull  = false;
                glDisable( GL_CULL_FACE );
            _alpha = false;
                glDisable( GL_ALPHA_TEST );
			_light  = false;
                glDisable( GL_LIGHTING );
			_depthT = true;
                glEnable( GL_DEPTH_TEST );

		}

		~this()
		{
			delete _curLights;
		}
}
